/**
 * 
 */
package edu.towson.cosc455.JohnCarloSalter.compiler;

import edu.towson.cosc.cosc455.interfaces.SyntaxAnalyzer;

import java.util.ArrayList;

/**
 * John Carlo Salter
 */

//THINGS TO DO:
//YOU NEED TO CODE IT INTO THE PROCESS METHODS TO MAKE THEM IGNORE WHITESPACE. 				-Done
//	-Done for:
//		-processTag();
//		-text();
	//NEW ISSUE HAS ARISEN: 																-Done
	//Currently, you're doing "empty" options by checking if the current token corresponds
	//to any of the possible options before processing those options. However, if the
	//current token is whitespace, it will not enter the conditional block and thus
	//it will register as empty.
//	^->Solution relegated to "tokenCompare" method.
//			//Issue has been resolved, but not very neatly.
//********************************************************************


public class MySyntaxAnalyzer implements SyntaxAnalyzer {

	//Parse Tree -- Consider making private.
	public ArrayList<String> parseTree;
	
	//Default Constructor
	public MySyntaxAnalyzer() {
		parseTree = new ArrayList<String>(); //Initialize the parse tree.
	}
	
	@Override
	//#HAI comment? head? body?  #KTHXBAI
	public void lolcode() {		
		//BEGIN
		processTag(LexicalTokens.DOC_BEGIN);		
		//COMMENT
		if(tokenCompare(LexicalTokens.COMMENT_BEGIN)) { comment(); }		
		//VARIABLE
		if(tokenCompare(LexicalTokens.VARIABLE_DEFINE_BEGIN)) { define(); }
		//HEAD
		if (tokenCompare(LexicalTokens.HEAD_BEGIN)) { head(); }
		//BODY -- Should always check for one.
		body();		
		//END
		processTag(LexicalTokens.DOC_END);
		
		verifyEndOfFile();

	} //End lolcode
	
	
	public void body() {
		if(!tokenCompare(LexicalTokens.DOC_END)) {
			//Check end of file
			if(endOfFile()) {
				endOfFileError();
			}
			
			//Check content.
			if(tokenCompare(LexicalTokens.COMMENT_BEGIN)) { comment(); }
			else if (tokenCompare(LexicalTokens.PARAGRAPH_BEGIN)) { paragraph(); }
			else if (tokenCompare(LexicalTokens.BOLD_BEGIN)) { bold(); }
			else if (tokenCompare(LexicalTokens.ITALICS_BEGIN)) { italics(); }
			else if (tokenCompare(LexicalTokens.LIST_BEGIN)) { list(); }
			else if( checkInnerText() ) { innerText(); }
			else {
				genericSyntaxError();
			}
			
			//Check for more body content.
			body(); 
		}
	}

	@Override
	public void comment() {
		if(tokenCompare(LexicalTokens.COMMENT_BEGIN)) {
			processTag(LexicalTokens.COMMENT_BEGIN);
			text();
			processTag(LexicalTokens.COMMENT_END);
		}
	}


	public void head() {
		processTag(LexicalTokens.HEAD_BEGIN);		
		title();		
		processTag(LexicalTokens.HEAD_END);
	}

	public void title() {
		if(tokenCompare(LexicalTokens.TITLE_BEGIN)) {
			processTag(LexicalTokens.TITLE_BEGIN);
			text();
			processTag(LexicalTokens.TITLE_END);
		}
	}


	public void paragraph() {
		if(tokenCompare(LexicalTokens.PARAGRAPH_BEGIN)) {
			processTag(LexicalTokens.PARAGRAPH_BEGIN);
			define();
			innerParagraph();
			processTag(LexicalTokens.PARAGRAPH_END);
		}
	}
	
	public void innerParagraph() {
		if(checkInnerText()) { innerText(); innerParagraph(); }
		if(tokenCompare(LexicalTokens.BOLD_BEGIN)) { bold(); innerParagraph(); }
		if(tokenCompare(LexicalTokens.ITALICS_BEGIN)) { italics(); innerParagraph(); }
		if(tokenCompare(LexicalTokens.LIST_BEGIN)) { list(); innerParagraph(); }
		if(tokenCompare(LexicalTokens.NEWLINE)) { newline(); innerParagraph(); }
	}

	public void bold() {

		processTag(LexicalTokens.BOLD_BEGIN);
			text();
		processTag(LexicalTokens.BOLD_END);
	
	}

	public void italics() {
		processTag(LexicalTokens.ITALICS_BEGIN);
		text();
		processTag(LexicalTokens.ITALICS_END);
	}

	public void list() {
		processTag(LexicalTokens.LIST_BEGIN);
		processTag(LexicalTokens.LIST_ITEM_BEGIN);
		innerList();
		processTag(LexicalTokens.LIST_ITEM_END);
		item();
		processTag(LexicalTokens.LIST_END);
	}

	//"List items" in the grammar.
	public void item() {
		if(tokenCompare(LexicalTokens.LIST_ITEM_BEGIN)) {
			processTag(LexicalTokens.LIST_ITEM_BEGIN);
			innerList();
			processTag(LexicalTokens.LIST_ITEM_END);
			item();
		}
		//else empty.
	}
	
	public void innerList() {
		if(tokenCompare(LexicalTokens.BOLD_BEGIN)) { bold(); innerList(); }
		if(tokenCompare(LexicalTokens.ITALICS_BEGIN)) { italics(); innerList(); }
		if(tokenCompare(LexicalTokens.LIST_BEGIN)) { list(); innerList(); }
		if(checkInnerText()) { innerText(); innerList(); }
		//if none of these, then it's empty.
	}

	public void audio() {
		if(tokenCompare(LexicalTokens.SOUND_BEGIN)) {
			processTag(LexicalTokens.SOUND_BEGIN);
			text();
			processTag(LexicalTokens.SOUND_END);
		}
	}

	public void video() {
		if(tokenCompare(LexicalTokens.VIDEO_BEGIN)) {
			processTag(LexicalTokens.VIDEO_BEGIN);
			text();
			processTag(LexicalTokens.VIDEO_END);
		}
	}

	public void define() {
		if(tokenCompare(LexicalTokens.VARIABLE_DEFINE_BEGIN)) {
			processTag(LexicalTokens.VARIABLE_DEFINE_BEGIN);
			text();
			processTag(LexicalTokens.VARIABLE_DEFINE_MIDDLE);
			text();
			processTag(LexicalTokens.VARIABLE_DEFINE_END);
			define(); //Recursively call this method to see if there are more variables.
		}
	}

	public void use() {
		if(tokenCompare(LexicalTokens.VARIABLE_USE_BEGIN)) {
			processTag(LexicalTokens.VARIABLE_USE_BEGIN);
			text();
			processTag(LexicalTokens.VARIABLE_USE_END);
		}
	}
	
	public void newline() {
		if(tokenCompare(LexicalTokens.NEWLINE)) {
			processTag(LexicalTokens.NEWLINE);
		}
	}
	
	public void innerText() {
		
		if(	checkInnerText() ) {
			
			//Check for a specific type of innerText.
			//Everything in this section has the "Empty" option.		
			use();
			newline();
			audio();
			video();
			text();
			
			//Check for more innerText.
			innerText();
		}
	}
	
	//If we're not actually looking at text...
		//If we're at the end of the file, throw an error and exit.
		//else return; we have a tag.
	//Else add the current token to the parse tree and get the next token.
		//This should account for whitespace as well.
		//Call text again to make sure there isn't more text.
	public void text() {
		if(!checkText()) {			
			if(endOfFile()) {
				endOfFileError();
			} else { //return -- we have a tag.
				return;
			}	
		} else { 
			addToParseTree();
			
			//UnitTester.lexer.getNextToken();
			//REINSTATE THE BELOW AFTER UNIT TESTING. -Done
			Compiler.lexer.getNextToken();
			
			text();
		}
	}
	//======================================================================================================================
	//==============================================UTILITY METHODS*********************************************************
	//======================================================================================================================
	
	//==Checkers==
	//Method to compare a String to the current token.
	//Returns true if the String is equal to the current token, disregarding case.
	private boolean tokenCompare(String s) {
		//Auto-process white space.
		//We're adding this functionality to this method as a sloppy way of refactoring.
		//If there's time, we will extract this functionality elsewhere.
		if(Compiler.token.equals(LexicalTokens.SPACE) || Compiler.token.equals(LexicalTokens.TAB) || Compiler.token.equals(LexicalTokens.NL)) {
			addToParseTree();
			
			//UnitTester.lexer.getNextToken();
			
			//REINSTANTE THE BELOW AFTER UNIT TESTING. -Done
			Compiler.lexer.getNextToken();
			tokenCompare(s); //Call this method again -- this allows us to ignore the white space.
		}
		
		return Compiler.token.equalsIgnoreCase(s);
	}
	
	//Method to see if we have reached the end of the file.
	//IF we are at the end of the file, we expect the token to be the empty string.
	//Returns true if we are at the end of the file.
	private boolean endOfFile() {
		if(Compiler.token.equals("")) {
			return true;
		} else {
			return false;
		}
	}
	
	//Returns true if the current token is <text>.
	//White space is considered text.
	private boolean checkText() {
		if(Compiler.token.contains("#") || Compiler.token.equals("")) {
			return false;
		} else {
			return true;
		}
	}
	
	//Returns true if the current token is <inner-text>.
	private boolean checkInnerText() {
		if(tokenCompare(LexicalTokens.VARIABLE_USE_BEGIN) ||
			tokenCompare(LexicalTokens.NEWLINE) ||
			tokenCompare(LexicalTokens.SOUND_BEGIN) ||
			tokenCompare(LexicalTokens.VIDEO_BEGIN) ||
			checkText() ) {
			
				return true;
		} else {
				return false;
		}
	}

//*******************************************************
//*******************************************************	
//==ERROR MESSAGES AND EXITTERS==
//*******************************************************
//*******************************************************	
	
	//Unexpected token error. Default assumes that Compiler.token was to be found.
	private void unexpectedTokenError(String expected) {
		System.err.println("\nERROR (Syntax Error): Expected \"" + expected + "\", found \"" + Compiler.token + "\"\n");
		//Thread.dumpStack();
		System.exit(-1);
	}
	/*private void unexpectedTokenError(String expected, String found) {
		System.err.println("\nERROR: Expected \"" + expected + "\", found \"" + found + "\"\n");
		Thread.dumpStack();
		System.exit(-1);
	}*/
	//End of file error
	private void endOfFileError() {
		System.err.println("\nERROR (Syntax Error): Reached end of file while parsing.");
		System.exit(-1);
	}
	private void reachedEndTagEarlyError() {
		System.err.println("\nERROR (Syntax Error): Reached end tag before end of document.");
		System.exit(-1);
	}
	private void genericSyntaxError() {
		System.err.println("\nERROR (Syntax Error): No valid token found.");
		System.err.println("Current Token: " + Compiler.token);
		System.exit(-1);
	}

//*******************************************************	
//==OTHER UTILITY METHODS FOR EASY MODIFICATION LATER==
//*******************************************************
	
	//Method to check for an expected tag and process said tag.
	//Add token to parse tree and gets the next token if the tag is correct.
	//This method was made partway through development of this class.
	//As such, it may not be completely refactored it yet.
	private void processTag(String expectedTag) {
		
		//Auto-process white space.
		if(tokenCompare(LexicalTokens.SPACE) || tokenCompare(LexicalTokens.TAB) || tokenCompare(LexicalTokens.NL)) {
			addToParseTree();
			Compiler.lexer.getNextToken();
			processTag(expectedTag); //Call this method again -- this allows us to ignore the white space.
		}
		
		if(!tokenCompare(expectedTag)) {
			unexpectedTokenError(expectedTag);
		} else {
			addToParseTree();
		}
		
		//Get the next token.
		//UnitTester.lexer.getNextToken();
		
		//Reinstate the below when testing is complete. -Done
		Compiler.lexer.getNextToken();
	}
	
	//Add the current token (or a specific string) to the parse tree.
	private void addToParseTree() {
		parseTree.add(Compiler.token);
	}
	
	private void verifyEndOfFile() {
		while(!endOfFile()) { //If we're not at the end of the file.
			if(!Compiler.token.equals(LexicalTokens.SPACE)
					&& !Compiler.token.equals(LexicalTokens.TAB)
					&& !Compiler.token.equals(LexicalTokens.NL)) { //If there's anything but whitespace.
				reachedEndTagEarlyError();
			}
			Compiler.lexer.getNextToken();
		}
	}

	
//***************************UNIT TESTING*******************************
	public void printParseTree() {
		for(int i=0; i<parseTree.size(); i++) {
			System.out.print(parseTree.get(i));
		}
	}
	
	public static void main(String[] args) {
		if(UnitTester.testMySyntaxAnalyzer()) {
			System.out.println("The Syntax Analyzer is working correctly!");
		}
	}
} //End MySyntaxAnalyzer



//***************************************************************************